verify-deployment --allow-unlocked, and both lockfiles regenerated on bun 1.4.0 - #12
Merged
Merged
Conversation
…nt to be unlocked verify-deployment.ts asserts two things and exits 0 only if BOTH hold: every on-chain verifier key is byte-identical to the committed build, and the maintenance authority is dissolved so the contract can never change. That is right for a hosted release. It is wrong for a devnet: a demo stack deliberately leaves the contract unlocked (SHIELDED_NIGHT_LOCK=false), because locking is a one-way door and a throwaway contract gains nothing from it. The consequence, measured against the live preprod contract e354e672… (11/11 keys matching, committee=1 threshold=1): the script prints eleven ✓ lines and then exits 1. The strongest check a demo stack can run is therefore unreadable from its exit code, which is exactly how both demo stacks consume it — as the exit status of a compose one-shot, not as parsed stdout. --allow-unlocked keeps measuring and printing the lock state, but lets only the code check decide the exit code. It never weakens that check: a key mismatch, a missing circuit or an extra circuit still exits 1 with the flag set. Unknown arguments are rejected rather than ignored, so a typo fails loudly instead of silently reverting to the strict behaviour. The policy lives in a new dependency-free scripts/verify-args.ts (the same seam pattern as scripts/deploy-record.ts) so the exit-code table is unit-testable without a chain: 17 new tests in the existing bun run test:unit tier, no new dependency, both lockfiles untouched. Default behaviour is unchanged.
…un 1.4.0
CI installs with an unpinned setup-bun@v2, so it now runs bun 1.4.0, and both
lockfiles here were written by an older bun. Regenerating them wholesale on
1.4.0 turned out to be impossible with package.json untouched, for two separate
reasons that were measured against oven/bun:1.4 (bun 1.4.0+34cbb9a40):
1. The root tree did not install at all. `bun install` exited non-zero with
"No version matching ^0.1.0-alpha.1 found for @midnight-ntwrk/ledger-v9".
The root declares compact-js ^2.5.1 — the only loose range in the midnight
family — and compact-js has since published 2.5.3, which replaced its
ledger-v8 dependency with ledger-v9 ^0.1.0-alpha.1, a range the registry no
longer serves. The committed lockfile hid this by pinning 2.5.1; deleting
it exposed it. Anyone adding a dependency at the root would have hit the
same wall.
2. Both trees forked the onchain-runtime WASM. compact-runtime 0.16.0 asks for
^3.0.0, now satisfied by the newly published 3.1.0, while
midnight-js-protocol 4.1.1 pins 3.0.0 exactly, so an unpinned resolve
installs both. Two copies of a WASM package give two class identities and
break each other's instanceof checks ("expected instance of ChargedState"
when reading ledger state) — which is precisely why onchain-runtime-v3 is
already named in frontend/vite.config.ts resolve.dedupe. On the Node side
there is no dedupe at all, so both copies would genuinely be loaded by the
integration suite, deploy.ts and verify-deployment.ts.
Three overrides fix both: compact-js 2.5.1 and onchain-runtime-v3 3.0.0 at the
root, onchain-runtime-v3 3.0.0 in frontend/. They are the same defensive pin the
file already applies to ledger-v8, and the //overrides note is expanded to say
what each entry is for. With them, both trees regenerate cleanly on 1.4.0: root
368 packages, frontend 270, exactly one onchain-runtime-v3@3.0.0 in each, and
the built SPA still emits exactly one ledger wasm and one onchain-runtime wasm.
The five versions that must not move did not move: ledger-v8 8.1.0,
compact-runtime 0.16.0, compact-js 2.5.1, midnight-js 4.1.1 and
dapp-connector-api 4.0.1. The only other midnight-family drift is
@midnightntwrk/wallet-sdk-utilities 1.2.0 -> 1.2.1 (inside the declared ^1.2.0)
with its nested @effect/platform 0.96.2 -> 0.96.3 at the root, and a
de-duplication in frontend/: wallet-sdk-address-format went from two copies
(3.1.0 plus a 3.1.2 nested under midnight-js-utils) to the single pinned 3.1.0.
Everything else is ordinary tooling: vitest 4.1.10 -> 4.1.11, vite 8.1.3 ->
8.2.2, rolldown 1.1.4 -> 1.2.7, rollup 4.62.2 -> 4.63.1, effect 3.21.4 ->
3.22.1, @noble/* 2.2.0 -> 2.4.0, @apollo/client -> 4.2.12, ws 8.21.0 -> 8.21.3.
Contract sources and src/managed are untouched: a full `bun run compact` with
compactc 0.31.1 reproduces src/managed byte for byte after the change.
NOTE FOR CONSUMERS: bun 1.4 writes lockfileVersion 2, which bun 1.3.x cannot
parse ("Unknown lockfile version"), and there is no flag to write the old
format. So the minimum bun for this repository is now 1.4.x. CI is already
there; anything that installs this tree with an older bun — the demo stacks'
images pin oven/bun:1.3.11 by digest — has to move to a 1.4.x base at the same
time it pins a commit from this branch.
acedward
added a commit
that referenced
this pull request
Sep 3, 2026
Brings in from main: PR #11 (frontend/.env PREPROD_ADDRESS=e354e672...) and PR #12 (scripts/verify-deployment.ts --allow-unlocked + scripts/verify-args.ts + tests + docs, plus a bun-1.4 lockfile regeneration with new root overrides compact-js 2.5.1 / onchain-runtime-v3 3.0.0 and a frontend onchain-runtime-v3 3.0.0 override). Conflict resolution: package.json / frontend/package.json kept this branch's own v9 overrides (ledger-v9 1.0.0-rc.3, compact-runtime 0.19.0, onchain-runtime-v4 4.0.0-rc.3, plus frontend's compact-js 2.5.5-rc.8) and documented in the //overrides comment why main's v8-line overrides (compact-js 2.5.1, ledger-v8 8.1.0, onchain-runtime-v3 3.0.0) do not apply to this dependency graph: ledger-v8 and onchain-runtime-v3 are not in this tree at all, and compact-js is already pinned exact here so needs no defensive override. bun.lock and frontend/bun.lock were not hand-merged: both were deleted and regenerated with bun 1.4.0 over a git-archive-style export (no root .env mounted or read), then verified frozen-installable, exactly one copy each of ledger-v9 / compact-runtime / onchain-runtime-v4 / compact-js in both trees, and zero copies of ledger-v8 / onchain-runtime-v3. Everything else (README, TESTING, frontend/.env, scripts/verify-deployment.ts, scripts/verify-args.ts, its unit test) auto-merged cleanly with no conflicts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No behaviour change, and nothing to migrate in the dApp.
bun run verify:deploymentwith no arguments does exactly what it did before; the contract,src/managed/and every served artifact are untouched (a fullcompact compile +0.31.1still reproducessrc/managedbyte for byte); and every version this repo pins is on the same version it was on before. What lands is an opt-in CLI flag with 17 unit tests and docs, plus three defensiveoverridesand a regeneration of both lockfiles.This PR carries both halves of the follow-up: the
--allow-unlockedflag, and the lockfile regeneration that was previously held pending a decision on whetherpackage.jsoncould be touched (it could — see §2).1.
--allow-unlockedforscripts/verify-deployment.tsverify-deployment.tsasserts two things and exits 0 only if both hold:src/managed/keys/*.verifier, and the circuit sets match exactly.That is right for a hosted release. It is wrong for a devnet: a demo stack deliberately leaves the contract unlocked (
SHIELDED_NIGHT_LOCK=false), because locking is a one-way door and a throwaway contract gains nothing from it.The consequence, measured against the live preprod contract
e354e6725893397e6a2dfa44522a017fabb5d9c92efed50288711f5f865c8950: the script prints eleven✓lines and then exits 1. So the strongest check a demo stack can run is unreadable from its exit code — which is exactly how both demo stacks consume it, as the exit status of a compose one-shot rather than as parsed stdout.--allow-unlockedkeeps measuring and printing the lock state, but lets only the code check decide the exit code.--allow-unlocked--allow-unlockedThe flag only ever changes what an unlocked contract does to the exit code. It never weakens the code check. Unknown arguments are rejected rather than ignored, so a typo (
--allow-unlock) fails loudly instead of silently reverting to strict behaviour and reading as "the flag does not work".Shape
The policy is extracted into a new dependency-free
scripts/verify-args.ts(parseVerifyArgs,verifyOutcome,lockVerdictLine) — the same seam pattern asscripts/deploy-record.ts.verify-deployment.tskeeps all the chain work and just calls it. That makes the exit-code table unit-testable with no chain, no docker and no new dependency.How it was verified
bun run test:unit→ 95 passed / 95 (7 files; was 78/78 in 6 — 17 new).bun run typecheckexit 0.Measured against the live preprod contract above (unlocked:
committee=1 threshold=1 counter=0; keys match), read-only, no wallet or seed:bun run verify:deployment✓ on-chain == local,✗ NOT locked,❌ verification FAILED… -- --allow-unlocked✓,ℹ NOT locked: … Reported only, not failed,✅ verified: … Lock state REPORTED ONLYsrc/managed/keys/getBalance.verifierflipped,-- --allow-unlocked✓/ 1✗ getBalance: on-chain 7981e854…a08416bf != local 66a1c38f…a7e6287dCV_ADDRESS=0000…0000,-- --allow-unlockedno contract state on chain--allow-unlock(typo)Unknown argument …Row 3 is the one that matters: the flag does not weaken the key check.
Docs
README.md→ new subsection under "Verifying the deployment": what it relaxes, what it never relaxes, sample output, why not to use it for a hosted release.TESTING.md→ "Verifying a deployment as a gate", with the exit-code table and a pointer to the unit test.2. Lockfile regeneration on bun 1.4.0, with three pinning
overridesCI installs with an unpinned
oven-sh/setup-bun@v2, so it now runs bun 1.4.0, while both lockfiles here were written by an older bun. The plan was to delete them and regenerate wholesale on 1.4.x withpackage.jsonuntouched. Measured withoven/bun:1.4= bun 1.4.0+34cbb9a40 (the newest release, i.e. exactly what CI installs) against agit archiveofmain@6d87db4, that is not possible — two independent defects surface, both in the family this repo pins on purpose.Defect 1 — the root tree does not install at all
The root declares
"@midnight-ntwrk/compact-js": "^2.5.1"— the only loose range in the midnight family.compact-jshas since published 2.5.3 (nowdist-tags.latest), and 2.5.3 replaced itsledger-v8: ^8.0.3dependency withledger-v9: ^0.1.0-alpha.1, a range that is no longer published (the v9 line moved to1.0.0-rc.x). The committed lockfile hid this by pinning 2.5.1; deleting it exposed it.This is a pre-existing landmine on
main, not something this branch introduces: a plainbun installat the repo root fails today, so only--frozen-lockfileis safe. And even if 2.5.3 did resolve, taking it would put two copies ofcompact-jsin the tree (2.5.3 direct, plus the 2.5.1 thatmidnight-js-protocol@4.1.1pins exactly) — the exact class-identity hazard the//overridesnote was written about.Defect 2 — both trees fork the onchain-runtime WASM
With
package.jsonuntouched,frontend/does regenerate (271 packages) with every pinned version intact — but@midnight-ntwrk/onchain-runtime-v3goes from one copy to two, in both trees:3.1.0hoisted plus3.0.0nested undermidnight-js-protocol@4.1.1, which pins it exactly whilecompact-runtime@0.16.0asks for^3.0.0(now satisfied by the newly published 3.1.0).onchain-runtime-v3is named infrontend/vite.config.ts'sresolve.dedupefor precisely this reason — its comment reads that two WASM instances give classes that "fail each other'sinstanceofchecks ("expected instance of ChargedState" when reading ledger state)". On the frontenddedupewould paper over it, but only by silently substituting 3.1.0 wheremidnight-js-protocolpinned 3.0.0. On the root/Node side — the integration suite,deploy.ts,verify-deployment.ts— there is no dedupe, so both copies genuinely load.The fix: three
overridesNeither override forces a version below anybody's floor. In both regenerated lockfiles every requirement for
compact-jsis either the exact2.5.1thatmidnight-js-protocol@4.1.1pins or the root's own^2.5.1, and every requirement foronchain-runtime-v3is either the exact3.0.0thatmidnight-js-protocol@4.1.1pins orcompact-runtime@0.16.0's^3.0.0— the pins satisfy all of them, and simply pick one of several admissible resolutions.They are the same defensive pin the file already applies to
ledger-v8, and the top-level//overridesnote is rewritten to say, per entry, which class identity it protects and why the pin is needed — so the next person does not have to re-derive it from an npm graph.Nothing that must hold, moved
@midnight-ntwrk/ledger-v8@midnight-ntwrk/compact-runtime@midnight-ntwrk/compact-js@midnight-ntwrk/midnight-js+ all ninemidnight-js-*@midnight-ntwrk/dapp-connector-api@midnight-ntwrk/onchain-runtime-v3@midnight-ntwrk/platform-js/zkir-v2@midnightntwrk/wallet-sdk+ 11 subpackagesEvery other
@midnight-ntwrk/*/@midnightntwrk/*change, in full@midnightntwrk/wallet-sdk-utilities(root)^1.2.0; the only midnight-family version change in either tree@effect/platform(root, nested under it)@midnight-ntwrk/wallet-sdk-address-format(frontend)midnight-js-utils@4.1.1(range^3.1.0) now collapses onto the pinned top-level 3.1.0. One fewer ledger-v8-bearing copy in the SPA — it is why the build transforms 1403 modules instead of 1448No
@effectstream/*package exists in either tree.Ordinary tooling drift
Root: 76 names changed, 1 added, 8 removed (376 → 369 distinct names). Frontend: 54 changed, 1 added, 1 removed (315 → 315).
vitest/@vitest/*viterolldown(+ bindings)rollup(+ bindings, frontend)effect@apollo/client@noble/hashes,@noble/ciphers,@noble/curves@scure/base,@scure/bip32,@scure/bip39graphql-ws/graphql-httpwsundicitestcontainerslightningcss(+ bindings)postcss@babel/*(frontend)caniuse-lite/electron-to-chromium/browserslistThe root removals are all wasm-fallback binding packages that
rolldown1.2.7 no longer pulls (@rolldown/binding-wasm32-wasi,@emnapi/*,@napi-rs/wasm-runtime,@tybys/wasm-util,bare-os,is-what), replaced by@rolldown/binding-android-arm-eabi.@types/nodeat the root goes from one copy to three (24.13.3 plus 18.19.130 and 26.4.1 nested under@types/ssh2/undici-types);bun run typecheckis green with it.How it was verified
Every run used bun
1.4.0+34cbb9a40in a short-livedoven/bun:1.4container over agit archiveexport of this branch.bun install --frozen-lockfile, rootbun.lockbyte-identical afterwardsbun install --frozen-lockfile,frontend/frontend/bun.lockbyte-identical afterwardsnode_modulesaudit after both installsonchain-runtime-v3(3.0.0),ledger-v8(8.1.0),compact-js(2.5.1)bun run compact(compactc 0.31.1) thengit diff --exit-code -- src/managedbun run typecheck(root)bun run test:unitfrontend:bun run typecheckfrontend:bun run builddist/assetscarries exactly two wasm files (one ledger, one onchain-runtime), theSHIELDED_NIGHTruntime-config marker survives minification,dist/config.jsstill emittedThe bun floor, measured
oven/bun:1.4(1.4.0+34cbb9a40)oven/bun:1.3.11(1.3.11+af24e281e)main@6d87db4(lockfileVersion: 1)lockfileVersion: 2), rootUnknown lockfile version→lockfile had changes, but lockfile is frozenlockfileVersion: 2),frontend/bun install --helpon 1.4.0 offers--save-text-lockfileand--lockfile-onlyand nothing that selects a lockfile version, so the bump is unavoidable once the lockfiles are regenerated on 1.4. Hence the warning at the top of this PR.Follow-ups this unblocks
midnight-1-offersscripts/verify-shielded-night.shviadocker compose run --rm shielded-night-verify, and itsmidnight-2-offerscounterpart).bun installat the root works again for the first time sincecompact-js2.5.3 was published, so adding a dependency no longer requires reverse-engineering an npm graph.BUN_BASEfromoven/bun:1.3.11to a 1.4.x digest in the same commit.